package com.violet.redis.aspect;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.violet.common.exception.BusinessException;
import com.violet.common.util.RegexUtils;
import com.violet.redis.anno.CacheDel;
import com.violet.redis.anno.CacheGet;
import com.violet.redis.anno.CacheSave;
import com.violet.redis.constant.RedisKeyUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @Author: wqf
 * @Date: 2023/07/20
 * @Description: 缓存相关注解aop实现类
 */
@Component
@Slf4j
@Aspect
public class CacheAspect {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 切入点表达式
     */
    @Pointcut(value = "execution(* com.violet.*.server.*.service.*.*(..))")
    public void cachePointCut() {
    }

    Object result = null;

    //环绕通知,环绕通知方法的返回值一定要和目标对象方法的返回值一致
    @Around(value = "cachePointCut()")
    public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint) {
        try {
            Signature signature = joinPoint.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            //获取方法
            Method method = methodSignature.getMethod();
            //获取参数
            Object[] args = joinPoint.getArgs();
            String cacheKey;

            //判断注解是否在方法上
            if (method.isAnnotationPresent(CacheDel.class)) {
                //删除缓存操作 == 前置通知操作
                CacheDel annotation = method.getAnnotation(CacheDel.class);
                String keyPrefix = annotation.keyPrefix();
                String value = annotation.keyValue();
                cacheKey = RedisKeyUtil.getCacheKey(keyPrefix, value);
                Boolean delete = redisTemplate.delete(cacheKey);
                if (delete != null && delete) {
                    log.info("redis数据删除成功==key:{}", cacheKey);
                } else {
                    log.error("redis数据删除失败==key:{}", cacheKey);
                }
            } else if (method.isAnnotationPresent(CacheGet.class)) {
                //查询缓存操作 前置通知操作
                CacheGet annotation = method.getAnnotation(CacheGet.class);
                String keyPrefix = annotation.keyPrefix();
                TimeUnit timeUnit = annotation.timeUnit();
                long expire = annotation.expire();
                cacheKey = RedisKeyUtil.getCacheKey(keyPrefix, getKeyValue(args, annotation.keyValue()));
                Object cacheValue = redisTemplate.opsForValue().get(cacheKey);
                if (ObjectUtil.isNotNull(cacheValue)) {
                    return cacheValue;
                }
                try {
                    result = joinPoint.proceed();
                } catch (Throwable e) {
                    log.error("获取方法返回值失败==={}", method.getName(), e);
                }
                //缓存值为空，数据库查询值不为空，则保存数据到缓存
                if (cacheValue == null && result != null) {
                    //缓存目标对象
                    redisTemplate.opsForValue().set(cacheKey, JSONUtil.toJsonStr(result));
                    //设置缓存时间
                    redisTemplate.expire(cacheKey, expire, timeUnit);
                }
                //缓存值为空，数据库查询也为空，则缓存一个空值，防止redis
                if (cacheValue == null && result == null) {
                    //缓存目标对象
                    redisTemplate.opsForValue().set(cacheKey, "");
                    //设置缓存时间 默认1分钟
                    redisTemplate.expire(cacheKey, 60, timeUnit);
                }
            } else if (method.isAnnotationPresent(CacheSave.class)) {
                //缓存数据操作
                CacheSave annotation = method.getAnnotation(CacheSave.class);
                String keyPrefix = annotation.keyPrefix();
                long expire = annotation.expire();
                TimeUnit timeUnit = annotation.timeUnit();
                cacheKey = RedisKeyUtil.getCacheKey(keyPrefix, getKeyValue(args, annotation.keyValue()));
                //获取方法返回值
                // 执行目标方法，并获取返回值
                result = joinPoint.proceed();
                // 处理返回值
                if (result != null) {
                    System.out.println("目标方法返回值：" + result);
                }
                //缓存目标对象
                redisTemplate.opsForValue().set(cacheKey, JSONUtil.toJsonStr(result));
                //设置缓存时间
                redisTemplate.expire(cacheKey, expire, timeUnit);
            } else {
                result = joinPoint.proceed();
            }
        } catch (Exception e) {
            throw new BusinessException(e.getMessage());
        } catch (Throwable e) {
            log.error("redisCache 执行目标方法异常====", e);
        }
        return result;
    }

    /**
     * 方法描述: 解析参数值
     *
     * @param args                 方法参数数组
     * @param paramIndexExpression 参数取值表达式 如 {0}
     * @return java.lang.String
     * @author wqf
     * @date 2023/7/21 10:33
     */
    private static String getKeyValue(Object[] args, String paramIndexExpression) {
        //解析参数取值表达式，获取参数数组下标
        String realValue = RegexUtils.getCurlyBracketValue(paramIndexExpression);
        if (!RegexUtils.isNonNegativeNumber(realValue)) {
            throw new BusinessException("=========KeyValue参数错误=========");
        }
        //方法参数下标
        int paramIndex = Integer.parseInt(realValue);
        Object keyValue = args[paramIndex];
        return String.valueOf(keyValue);
    }

}
